package com.keguoyu.easymvp.business;

import static com.keguoyu.easymvp.business.Business.BusinessState.BIND;
import static com.keguoyu.easymvp.business.Business.BusinessState.CREATE;
import static com.keguoyu.easymvp.business.Business.BusinessState.DESTROY;
import static com.keguoyu.easymvp.business.Business.BusinessState.UNBIND;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.view.View;

import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.fragment.app.Fragment;

import com.google.common.base.Preconditions;
import com.keguoyu.easymvp.commons.supplier.SupplierContainer;
import com.keguoyu.easymvp.receiver.AutoWiredReceiver;

import butterknife.ButterKnife;
import butterknife.Unbinder;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;

/**
 * Business是一种更细粒度的Presenter，一个Business
 * 用来表示单个的子业务。通过
 * 组合这些Business来完成复杂的业务。Business分为Root
 * Business和子Business，Root Business被外部调用从而
 * 触发子Business的生命周期
 *
 * 一个Business共有四个生命周期，分别是create，bind，
 * unbind，destroy。
 *
 * create：负责视图的绑定操作，绑定完成将回掉onCreate()
 *
 * bind：数据绑定操作，绑定完成将回调onBind()，bind是可
 * 重入的方法
 *
 * unbind：解绑定操作，会将数据reset，但是可以再次bind
 * 进入绑定状态
 *
 * destroy：销毁状态，视图解除绑定。
 *
 * 回掉onCreate()的时候所有的view会初始化完成，但是这时候的
 * 数据还没有进行绑定，如果在这时候使用@Receive注入的变量，那
 * 么将会出现数据为空的情况。所以我们通常一般在onBind()中进
 * 性具体的逻辑处理。
 *
 * Business表示一个具体的子逻辑，但是通常来说可能各个子逻辑
 * 之间可能会有某些数据的通信，所以这时候需要用到RxJava，我们
 * 不能直接通过一个Business引用另一个Business的方式来进行数
 * 据的通信。所以我们通常会在一个Business中subscribe，那么这
 * 时候我们需要使用这样的代码来避免内存泄漏的产生。
 *
 * <code>
 *   addToAutoDisposable(mSubject.subscribe(new Consumer<Boolean>() {
 *
 *       @Override
 *       public void accept(Boolean aBoolean) throws Exception {
 *          //your our action
 *       }
 *     }));
 *
 * </code>
 *
 * 通过addToAutoDisposable()添加的订阅，Root Business会在onUnbind()
 * 的时候统一进行解除订阅，避免内存泄漏的发生。
 *
 * 使用范例(以Fragment为例：)
 * <code>
 *    onViewCreated()方法：
 *    mRootBusiness = new Business();
 *    mRootBusiness.add(new SubBusiness1());
 *    mRootBusiness.add(new SubBusiness2());
 *    mRootBusiness.add(new SubBusiness3());
 *    ....
 *    mRootBusiness.create(view);
 *    mRootBusiness.bind(this);
 *
 *    onViewDestroyed()方法：
 *    mRootBusiness.unBind();
 *    mRootBusiness.destroy();
 * </code>
 *
 * 所有的SubBusiness都将有mRootBusiness统一装配，组成所有的功能。
 *
 * @author keguoyu
 * @version 1.0.2
 */
public class Business implements ViewFinder {
  /**
   * 当前Root Business所有容纳的子业务
   */
  private final ArrayList<Business> mChildren = new ArrayList<>();

  /**
   * 每一个Business都有属于自己的自动注入类，扫描出当前类的注入帮助类
   */
  private final AutoWiredExtension mAutoWiredExtension = new AutoWiredExtension(this);

  private final List<AutoWiredReceiver> mAutoWiredReceivers = new ArrayList<>();

  /**
   * 用于RxJava Disposable自动dispose用，使用addToAutoDisposable()添加的disposable能够
   * 在onUnBind()的时候自动解除订阅
   */
  private final CompositeDisposable mCompositeDisposable = new CompositeDisposable();

  private final BusinessContext mBusinessContext = new BusinessContext();

  private volatile BusinessState mState = BusinessState.INIT;

  @Nullable
  private Unbinder mUnbinder;

  /**
   * 是否是根业务
   * 主要当前business没有通过add()方法添加到别的business 它就算根
   */
  private boolean mIsRoot = true;

  /**
   * 是否需要开启Butter-knife绑定view
   * 默认关闭 Butter-knife不再推荐使用
   */
  private boolean mBindViewByButterKnife;

  public Business() {
    this(false);
  }

  public Business(boolean bindViewByButterKnife) {
    this.mBindViewByButterKnife = bindViewByButterKnife;
    addToProvider(this);
  }

  /**
   * 执行你自己的绑定view
   * 如果你使用的ViewBinding可不实现
   *
   * @param view rootView
   */
  protected void doFindViewById(@NonNull View view) {
  }

  /**
   * 执行View的绑定 在onCreate之前完成
   */
  @Override
  public final void doFindView(@NonNull View view) {
    if (mBindViewByButterKnife) {
      mUnbinder = ButterKnife.bind(this, view);
    } else {
      doFindViewById(view);
    }
  }

  @UiThread
  public final void create(Activity activity) {
    create(activity.getWindow().getDecorView());
  }

  @UiThread
  public final void create(Fragment fragment) {
    create(fragment.getView());
  }

  @UiThread
  public final void create(View rootView) {
    Preconditions.checkNotNull(rootView, "rootView can't be null");
    updateView(rootView);
    goToState(mState, CREATE);
  }

  private void updateView(View view) {
    mBusinessContext.mView = view;
  }

  private void updateData(Object...objects) {
    mBusinessContext.mObjects = objects;
  }

  private void createInternal() {
    checkDuplicateCreate();
    doFindView(mBusinessContext.mView);
    for (Business business : mChildren) {
      createChildren(business);
    }
    mState = CREATE;
  }

  private void createChildren(Business business) {
    business.updateView(mBusinessContext.mView);
    business.createInternal();
  }

  /**
   * onCreate()方法是Business的第一个生命周期，当这个方法
   * 被调用，所有的view应该已就绪
   */
  protected void onCreate() {
  }

  @UiThread
  public final void bind(Object...objects) {
    updateData(objects);
    goToState(mState, BusinessState.BIND);
  }

  private void bindInternal() {
    throwIfNotCreate();
    SupplierContainer supplierContainer =
        mAutoWiredExtension.buildSupplierContainer(mBusinessContext.mObjects);
    mAutoWiredExtension.receive(supplierContainer);

    autoWireReceivers(supplierContainer);
    bindChildren(supplierContainer);

    mState = BIND;
  }

  private void bindChildren(SupplierContainer supplierContainer) {
    Object[] objects = new Object[]{supplierContainer};
    for (Business business : mChildren) {
      business.updateData(objects);
      business.bindInternal();
    }
  }

  /**
   * 第二个生命周期方法，当这个方法被调用的时候，所有的数据已经注入完毕
   */
  protected void onBind() {
  }

  @UiThread
  public final void unBind() {
    goToState(mState, UNBIND);
  }

  private void unBindInternal() {
    mAutoWiredExtension.reset();
    for (Business business : mChildren) {
      business.unBindInternal();
    }
    mState = UNBIND;
  }

  protected void onUnBind() {
  }

  @UiThread
  public final void destroy() {
    goToState(mState, DESTROY);
  }

  private void destroyInternal() {
    mCompositeDisposable.clear();
    if (mUnbinder != null) {
      mUnbinder.unbind();
    }
    for (Business business : mChildren) {
      business.destroyInternal();
    }
    mState = BusinessState.DESTROY;
  }

  protected void onDestroy() {
  }

  @Nullable
  public final Activity getActivity() {
    Context context = getContext();
    while (context instanceof ContextWrapper) {
      if (context instanceof Activity) {
        return (Activity) context;
      }
      context = ((ContextWrapper) context).getBaseContext();
    }
    return null;
  }

  @Nullable
  public final Context getContext() {
    return mBusinessContext.mView  == null ?  null : mBusinessContext.mView.getContext();
  }

  public final void addToAutoDisposable(Disposable disposable) {
    mCompositeDisposable.add(disposable);
  }

  public void add(Business business) {
    Preconditions.checkNotNull(business, "Business不能为null");
    onNewChildBusiness(business);
    if (isCreate() && !business.isCreate()) {
      matchCreateState(business);
    }
  }

  private void autoWireReceivers(SupplierContainer supplierContainer) {
    for (AutoWiredReceiver autowiredReceiver : mAutoWiredReceivers) {
      autowiredReceiver.autoWire(supplierContainer);
    }
  }

  public void addAutoWiredReceiver(AutoWiredReceiver autowiredReceiver) {
    mAutoWiredReceivers.add(autowiredReceiver);
  }

  private void onNewChildBusiness(Business business) {
    mChildren.add(business);
    business.mIsRoot = false;
  }

  private void addToProvider(Business business) {
    mAutoWiredExtension.addToProvider(business);
  }

  private void matchCreateState(Business business) {
    business.create(mBusinessContext.mView);
    business.onCreate();
  }

  /**
   * 从当前状态到目标状态
   *
   * @param fromState 当前状态
   * @param toState 目标状态
   */
  private void goToState(BusinessState fromState, BusinessState toState) {
    BusinessStateController.goToState(fromState, toState,
        state -> {
          BusinessAction action =  fromStateToAction(state);
          callEntryAction(action);
          if (mIsRoot) {
            callOnState(action);
          }
        });
  }

  /**
   * 调用自己的internal()方法
   *
   * @param action 目标方法
   */
  private void callEntryAction(BusinessAction action) {
    if (action != null) {
      action.performActionInternal(this);
    }
  }

  /**
   * 如果是BusinessGroup类型的，除了调用自己的on生命周期，还需要
   * 调用子business的生命周期
   *
   * @param action 目标方法
   */
  private void callOnState(BusinessAction action) {
    action.performCallActionState(this);
    for (Business business : mChildren) {
      business.callOnState(action);
    }
  }

  private void checkDuplicateCreate() {
    if (isCreate()) {
      throw new IllegalStateException("仅能被Create一次");
    }
  }

  private void throwIfNotCreate() {
    if (!isCreate()) {
      throw new IllegalStateException("还没有被初始化");
    }
  }

  private boolean isCreate() {
    return mState.index() >= CREATE.index();
  }

  public static <T extends View> T bindWidget(@NonNull View view, @IdRes int viewId) {
    return view.findViewById(viewId);
  }

  public static void bindClickListener(@NonNull View view, @IdRes int viewId, View.OnClickListener onClickListener) {
    bindClickListener(bindWidget(view, viewId), onClickListener);
  }

  public static void bindClickListener(@Nullable View target, View.OnClickListener onClickListener) {
    if (target != null) {
      target.setOnClickListener(onClickListener);
    }
  }

  public static void bindLongClickListener(@NonNull View view, @IdRes int viewId, View.OnLongClickListener onLongClickListener) {
    bindLongClickListener(bindWidget(view, viewId), onLongClickListener);
  }

  public static void bindLongClickListener(@Nullable View target, View.OnLongClickListener onLongClickListener) {
    if (target != null) {
      target.setOnLongClickListener(onLongClickListener);
    }
  }

  private BusinessAction fromStateToAction(BusinessState state) {
    switch (state) {
      case CREATE:
        return BusinessActionImpl.ACTION_CREATE;
      case BIND:
        return BusinessActionImpl.ACTION_BIND;
      case UNBIND:
        return BusinessActionImpl.ACTION_UNBIND;
      case DESTROY:
        return BusinessActionImpl.ACTION_DESTROY;
      default:
        return BusinessActionImpl.ACTION_NONE;
    }
  }

  public enum BusinessActionImpl implements BusinessAction {
    ACTION_NONE {
      @Override
      public void performActionInternal(Business business) {
      }

      @Override
      public void performCallActionState(Business business) {
      }
    },
    ACTION_CREATE {
      @Override
      public void performActionInternal(Business business) {
        business.createInternal();
      }

      @Override
      public void performCallActionState(Business business) {
        business.onCreate();
      }
    },
    ACTION_BIND {
      @Override
      public void performActionInternal(Business business) {
        business.bindInternal();
      }

      @Override
      public void performCallActionState(Business business) {
        business.onBind();
      }
    },
    ACTION_UNBIND {
      @Override
      public void performActionInternal(Business business) {
        business.unBindInternal();
      }

      @Override
      public void performCallActionState(Business business) {
        business.onUnBind();
      }
    },
    ACTION_DESTROY {
      @Override
      public void performActionInternal(Business business) {
        business.destroyInternal();
      }

      @Override
      public void performCallActionState(Business business) {
        business.onDestroy();
      }
    }
  }

  interface BusinessAction {
    void performActionInternal(Business business);

    void performCallActionState(Business business);
  }

  enum BusinessState {
    NONE(-1),
    INIT(0),
    CREATE(1),
    BIND(2),
    UNBIND(3),
    DESTROY(4);

    private int mIndex;

    BusinessState(int index) {
      this.mIndex = index;
    }

    public int index() {
      return mIndex;
    }

    interface BusinessStateCallBack {
      void onState(BusinessState state);
    }
  }

  private static class BusinessContext {
    View mView;
    Object[] mObjects;
  }

}
